// This is the main DLL file.

#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <Windows.h>

#include "NativeConnector.h"

using namespace std;

using namespace System::Diagnostics;

using namespace Tradex::Connectivity;
using namespace Tradex::Connectivity::ConnectorModel;

using namespace Tradex;
using namespace Tradex::Market;

#define GOOD 0
#define BAD  1

namespace Tradex {
	namespace Connectivity {
		namespace NxCore {

			NativeConnector::NativeConnector (NxCoreConnector^ pManagedConnector) {
				pManaged = pManagedConnector;

				NxCoreThreadStatus = Stopped;
			}

			NativeConnector::~NativeConnector () {
				pManaged = NULL;
			}

			void NativeConnector::Connect (char* pTape) {
				hLib = ::LoadLibrary("NxCoreAPI.dll");
				if (hLib == 0) {
					// TODO: Thisneeds to be handled better - raising a managed exception, for example.
					pManaged->OnStatusUpdate (
						ConnectionStatusCode::Error,
						"NxCoreAPI.dll loading error"
					);
					pManaged->OnLogEntry (
						System::Diagnostics::TraceLevel::Error,
						nullptr,
						"NxCoreAPI.dll loading error",
						nullptr,
						nullptr
					);
					return;
				}
				pfNxProcessTape = (NxCoreProcessTape)::GetProcAddress(hLib, cszNxCoreProcessTape);
				pfNxPriceToDouble = (NxCorePriceToDouble)::GetProcAddress(hLib, cszNxCorePriceToDouble);
				pfNxCoreGetDefinedString = (NxCoreGetDefinedString)::GetProcAddress(hLib, cszNxCoreGetDefinedString);

				pfNxGetSymbolAtom32 = (NxCoreGetSymbolAtom32)::GetProcAddress(hLib, cszNxCoreGetSymbolAtom32);

				if (pTape != nullptr) {
					int sLen = strlen(pTape)+1;
					_Tape = (char*) malloc (sLen);
					if (_Tape == nullptr) {
						// TODO: This needs to be handled better - raising a managed exception, for example.
						pManaged->OnStatusUpdate (
							ConnectionStatusCode::Error,
							"Memory Allocation Error"
						);
						::FreeLibrary(hLib);
						return;
					}
					strcpy (_Tape, pTape);
				} else {
					_Tape = nullptr;
				}

				NxCoreThreadStatus = Starting;
				CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) NxCoreThread, this, 0, NULL); 
			}

			void NativeConnector::Disconnect () {
				if (NxCoreThreadStatus == Stopped) {
					return;
				}
				while (NxCoreThreadStatus != Stopped) {
					NxCoreThreadStatus = Stopping;
					Sleep(250);
				}
				::FreeLibrary(hLib);
			}

			DWORD NativeConnector::NxCoreThread (LPVOID lParam) {
				NativeConnector *me = (NativeConnector*) lParam;
				me->NxCoreThreadStatus = Running;
				while (me->NxCoreThreadStatus == Running) {
					// As long as we are to be kept running, we will just loop processing new threads.
					me->pCache = nullptr;
					int result = me->pfNxProcessTape(me->_Tape,0,0, (int) me,NxCoreCallback);
					if (me->_Tape) {
						me->NxCoreThreadStatus = Stopping;
					}
					if (result != 0) {
						// Ok, we had an error. We stop it.
						me->NxCoreThreadStatus = Stopping;
						me->pManaged->OnStatusUpdate (
							ConnectionStatusCode::Error,
							"Tape processing error"
						);
					}
				}
				me->NxCoreThreadStatus = Stopped;
				me->pManaged->OnLogEntry (
					System::Diagnostics::TraceLevel::Info,
					nullptr,
					"NxCoreAPI connector stopped",
					nullptr,
					nullptr
				);

				if (me->_Tape != nullptr) {
					free (me->_Tape);
				}

				return 0;
			}

			int __stdcall NativeConnector::NxCoreCallback(const NxCoreSystem* pNxCoreSys,const NxCoreMessage* pNxCoreMsg) {
				NativeConnector *me = (NativeConnector*) pNxCoreSys->UserData;
				if (me->NxCoreThreadStatus != Running) {
					me->pManaged->OnLogEntry (
						System::Diagnostics::TraceLevel::Info,
						nullptr,
						"NxCoreAPI.dll connector stopping",
						nullptr,
						nullptr
					);
					return NxCALLBACKRETURN_STOP;
				}
				try {
					return me->OnNxCoreCallback (pNxCoreSys, pNxCoreMsg);
				} catch (Exception^ ex) {
					return NxCALLBACKRETURN_STOP;
				}
			}

			int __stdcall NativeConnector::OnNxCoreCallback(const NxCoreSystem* pNxCoreSys,const NxCoreMessage* pNxCoreMsg) {
				// Preprocessing. If we get a non status message, and are not yet initialized, it is time to initialize string tables.
				if (pNxCoreMsg->MessageType != NxMSG_STATUS && !pCache) {
					ProcessNxCoreInitialized ();
					pCache->SetTimestamp (pNxCoreSys);
				}
				switch( pNxCoreMsg->MessageType ) {
					case NxMSG_STATUS:       
						return ProcessNxCoreStatus (pNxCoreSys);
					case NxMSG_EXGQUOTE:
						return ProcessNxCoreExgQuote (pNxCoreSys, pNxCoreMsg);
					case NxMSG_MMQUOTE:
						return ProcessNxCoreMmQuote (pNxCoreSys, pNxCoreMsg);
					case NxMSG_TRADE:        
						return ProcessNxCoreTrade (pNxCoreSys, pNxCoreMsg);
					case NxMSG_CATEGORY:
						return ProcessNxCoreCategory (pNxCoreSys, pNxCoreMsg);
					case NxMSG_SYMBOLCHANGE:
						return ProcessNxCoreSymbolChange (pNxCoreSys, pNxCoreMsg);
					case NxMSG_SYMBOLSPIN:
						return ProcessNxCoreSymbolSpin (pNxCoreSys, pNxCoreMsg);
				}
				return NxCALLBACKRETURN_CONTINUE;
			}

			void NativeConnector::ProcessNxCoreInitialized () {
				// Initialization. Time to get a certain amount of.... well... string tables filled...
				// THis is done in the NxCoreCache constructor...
				pCache = gcnew NxCoreCache (pfNxCoreGetDefinedString);
			}

			int NativeConnector::ProcessNxCoreStatus (const NxCoreSystem* pNxCoreSys) {
				// We set a centrally used timestamp here.
				if (pNxCoreSys->ClockUpdateInterval != NxCLOCK_NOCHANGE && pCache) {
					pCache->SetTimestamp (pNxCoreSys);
				}
				switch (pNxCoreSys->Status) {
					case NxCORESTATUS_RUNNING:
						// all is well.
						break;
					case NxCORESTATUS_INITIALIZING:
						pManaged->OnStatusUpdate (
							ConnectionStatusCode::Connecting,
							"NxCoreAPI.dll initializing"
						);
						break;
					case NxCORESTATUS_COMPLETE:
						pManaged->OnStatusUpdate (
							ConnectionStatusCode::Disconnected,
							"NxCoreAPI.dll tape complete"
						);
						break;
					case NxCORESTATUS_ERROR:
						pManaged->OnStatusUpdate (
							ConnectionStatusCode::Error,
							"NxCoreAPI.dll internal error"
						);
						return NxCALLBACKRETURN_STOP;
				}
				return NxCALLBACKRETURN_CONTINUE;
			}

			int NativeConnector::ProcessNxCoreExgQuote (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				InstrumentKey^ pInstrumentKey = nullptr;
				Activity^ pActivity = nullptr;

				InstrumentCache^ pInstrumentCache = pCache->GetInstrumentCache (pNxCoreSys, pNxCoreMsg, pfNxGetSymbolAtom32());
				pInstrumentKey = pInstrumentCache->MyInstrumentKey;

				DateTime timestamp = pCache->Timestamp;

				const NxCoreQuote&  cq = pNxCoreMsg->coreData.ExgQuote.coreQuote;

				if (cq.BidPriceChange || cq.BidSizeChange) {
					// Best Bid changed
					double price = pfNxPriceToDouble (
						pNxCoreMsg->coreData.ExgQuote.coreQuote.BidPrice,
						pNxCoreMsg->coreData.ExgQuote.coreQuote.PriceType
					);

					UInt64 volume = cq.BidSize;

					pActivity = gcnew Activity (timestamp, ActivityType::BestBid, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}
				if (cq.AskPriceChange || cq.AskSizeChange) {
					// Best Ask changed.
					double price = pfNxPriceToDouble (
						pNxCoreMsg->coreData.ExgQuote.coreQuote.AskPrice,
						pNxCoreMsg->coreData.ExgQuote.coreQuote.PriceType
					);

					UInt64 volume = cq.AskSize;
					
					pActivity = gcnew Activity (timestamp, ActivityType::BestAsk, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}
				return NxCALLBACKRETURN_CONTINUE;
			}

			int NativeConnector::ProcessNxCoreMmQuote (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				InstrumentKey^ pInstrumentKey = nullptr;
				Activity^ pActivity = nullptr;

				InstrumentCache^ pInstrumentCache = pCache->GetInstrumentCache (pNxCoreSys, pNxCoreMsg, pfNxGetSymbolAtom32());
				pInstrumentKey = pInstrumentCache->MyInstrumentKey;

				DateTime timestamp = pCache->Timestamp;

				const NxCoreHeader&   ch = pNxCoreMsg->coreHeader;
				const NxCoreMMQuote&  mm = pNxCoreMsg->coreData.MMQuote;
				const NxCoreQuote&    cq = mm.coreQuote;

				// First we need to identify the market maker. This is a astring "D1" to "D10".
				String^ pMarketMaker = gcnew String (mm.pnxStringMarketMaker->String); //String for debugging help;)
				int domLevel = 0;
				if (mm.pnxStringMarketMaker->String[0] == 'D') {
					switch (mm.pnxStringMarketMaker->String[1]) {
						case '1':
							if (mm.pnxStringMarketMaker->String[2] == 0) {
								domLevel = 1;
							} else {
								if (mm.pnxStringMarketMaker->String[2] == '0') {
									domLevel = 10;
								}
							}
							break;
						case '2':
							domLevel = 2;
							break;
						case '3':
							domLevel = 3;
							break;
						case '4':
							domLevel = 4;
							break;
						case '5':
							domLevel = 5;
							break;
						case '6':
							domLevel = 6;
							break;
						case '7':
							domLevel = 7;
							break;
						case '8':
							domLevel = 8;
							break;
						case '9':
							domLevel = 9;
							break;
					}
					switch (domLevel) {
						case 0:
							// Should not happen.
							return NxCALLBACKRETURN_CONTINUE;
							break;
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
						case 6:
						case 7:
						case 8:
						case 9:
							if (mm.pnxStringMarketMaker->String[2] != 0) {
								return NxCALLBACKRETURN_CONTINUE; // NOT a MM - something like D211 or so...
							}
							break;
						case 10:
							if (mm.pnxStringMarketMaker->String[3] != 0) {
								return NxCALLBACKRETURN_CONTINUE; // NOT a MM - something like D105 or so...
							}
							break;
					}
				} else {
					// NOT a level 2 quote. We will have to deal these at a later stage.
					return NxCALLBACKRETURN_CONTINUE;
				}

				double bidPrice = pfNxPriceToDouble (
					pNxCoreMsg->coreData.MMQuote.coreQuote.BidPrice,
					pNxCoreMsg->coreData.MMQuote.coreQuote.PriceType
				);
				UInt64 bidSize = pNxCoreMsg->coreData.MMQuote.coreQuote.BidSize;
				double askPrice = pfNxPriceToDouble (
					pNxCoreMsg->coreData.MMQuote.coreQuote.AskPrice,
					pNxCoreMsg->coreData.MMQuote.coreQuote.PriceType
				);
				UInt64 askSize = pNxCoreMsg->coreData.MMQuote.coreQuote.AskSize;

				if (cq.BidPriceChange || cq.BidSizeChange) {
					pActivity = gcnew Activity (timestamp, ActivityType::Bid, bidPrice, bidSize);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}

				if (cq.AskPriceChange || cq.AskSizeChange) {
					pActivity = gcnew Activity (timestamp, ActivityType::Ask, askPrice, askSize);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}

				//Console::WriteLine (pInstrumentKey + " MM " + pMarketMaker + ": " + bidSize + " @ " + bidPrice + " vs " + askSize + " @ " + askPrice );
				return NxCALLBACKRETURN_CONTINUE;
			}

			int NativeConnector::ProcessNxCoreTrade (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				InstrumentKey^ pInstrumentKey = nullptr;
				Activity^ pActivity = nullptr;

				InstrumentCache^ pInstrumentCache = pCache->GetInstrumentCache (pNxCoreSys, pNxCoreMsg, pfNxGetSymbolAtom32());
				pInstrumentKey = pInstrumentCache->MyInstrumentKey;
				DateTime timestamp = pCache->Timestamp;

				const NxCoreTrade& td = pNxCoreMsg->coreData.Trade;

				double price = pfNxPriceToDouble (td.Price, td.PriceType);

				UInt64 volume = 0;
				if (td.PriceFlags & NxTPF_SETLAST) {
					if (td.VolumeType == NxTVT_INCRVOL) {
						volume = td.Size;
					}

					pActivity = Activity::ForTrade (timestamp, price, volume, td.ExgSequence);
					//pActivity = gcnew Activity (timestamp, ActivityType::Trade, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);

					if (td.VolumeType == NxTVT_INCRVOL) {
						// We also ned to report the new tick volume here.
						UInt64 tickVolume = td.TickVolume;
						pActivity = gcnew Activity (timestamp, ActivityType::Volume, 0, tickVolume);
						pManaged->OnActivity (pInstrumentKey, pActivity);
					}
				}
				if (td.PriceFlags & NxTPF_SETHIGH) {
					// We also need to set the high here.
					pActivity = gcnew Activity (timestamp, ActivityType::High, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}
				if (td.PriceFlags & NxTPF_SETLOW) {
					/// We also need to set the low here.
					pActivity = gcnew Activity (timestamp, ActivityType::Low, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}
				if (td.PriceFlags & NxTPF_SETOPEN) {
					pActivity = gcnew Activity (timestamp, ActivityType::Open, price, volume);
					pManaged->OnActivity (pInstrumentKey, pActivity);
				}
				return NxCALLBACKRETURN_CONTINUE;
			}

			int NativeConnector::ProcessNxCoreCategory (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				InstrumentKey^ pInstrumentKey = nullptr;

				InstrumentCache^ pInstrumentCache = pCache->GetInstrumentCache (pNxCoreSys, pNxCoreMsg, pfNxGetSymbolAtom32());
				pInstrumentKey = pInstrumentCache->MyInstrumentKey;

				NxCategoryField *pField;
				String ^item;
				switch (pNxCoreMsg->coreData.Category.pnxStringCategory->Atom) {
					case 4:	{ //EquitySymbolInfo - we need this.
						String^ pDescription;
						pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						if (pField->Set) {
							pDescription = gcnew String (pField->data.StringZ);
						}
						InstrumentInfo^ pInstrumentInfo = gcnew InstrumentInfo (
							pInstrumentCache->MyInstrumentType,
							pDescription
						);
						pManaged->OnReferenceUpdate (pInstrumentKey, pInstrumentInfo, nullptr);
						// More information here that we do not deal with at the moment. Equity Classification could be interesting.
						} break;
					case 7: { //IndexSymbolInfo - we need this.
						String^ pDescription;
						pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						if (pField->Set) {
							pDescription = gcnew String (pField->data.StringZ);
						}
						InstrumentInfo^ pInstrumentInfo = gcnew InstrumentInfo (
							pInstrumentCache->MyInstrumentType,
							pDescription
						);
						pManaged->OnReferenceUpdate (pInstrumentKey, pInstrumentInfo, nullptr);
						} break;
					case 13: //52WeekHiLo - we ignore this.
					case 15: //ContractHiLo - we ignore them so far.
					case 16: //OHLC - we ignore them so far.
					case 17: //NxRefreshOverride - we will need them, but not right now.
					case 18: //NxUncorrectedOHLC - we ignore this.
					case 20: //TradeReportSummary - we ignore them so far.
					case 21: //FormTReportSummary - we ignore them so far.
					case 22: //OpeningTradeReports - we ignore them so far.
					case 29: //NxLastQuote - we ignore this.
					case 30: //SymbolHIstory - we ignore this. Can come in handy, but a lot later (plus w get daily updates anyway).
					case 33: //Activity Rank - we ignore this.
						break;
					case 42: //TradeSessionHours . we need this, but there is one item missing (trade session identifier).
						// This will get sense in a second release.
						pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						if (pField->Set) {
							Console::WriteLine(String::Format("Open Time: {0} {1} {2}",
									pField->data.nxTime.Hour,
									pField->data.nxTime.Minute,
									pField->data.nxTime.Second
							));
						}
						pField=&pNxCoreMsg->coreData.Category.pnxFields[1];
						if (pField->Set) {
							Console::WriteLine(String::Format("Close Time: {0} {1} {2}",
									pField->data.nxTime.Hour,
									pField->data.nxTime.Minute,
									pField->data.nxTime.Second
							));
						}
						break;
					case 68: //VolOI - we ignore them so far.
						break;
					case 70: { // RootSymbolInfo - we use those
						String^ pDescription;
						pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						if (pField->Set) {
							pDescription = gcnew String (pField->data.StringZ);
						}

						pField=&pNxCoreMsg->coreData.Category.pnxFields[1];
						if (pField->Set) {
							//Console::WriteLine(String::Format("Open Time: {0} {1} {2}",
							//		pField->data.nxTime.Hour,
							//		pField->data.nxTime.Minute,
							//		pField->data.nxTime.Second
							//));
						}
						pField=&pNxCoreMsg->coreData.Category.pnxFields[2];
						if (pField->Set) {
							//Console::WriteLine(String::Format("Close Time: {0} {1} {2}",
							//		pField->data.nxTime.Hour,
							//		pField->data.nxTime.Minute,
							//		pField->data.nxTime.Second
							//));
						}

						pField=&pNxCoreMsg->coreData.Category.pnxFields[8];	 
						if (pField->Set) {
							//item = gcnew String (pField->data.StringZ);
							//Console::WriteLine ("Contract Size: " + item);
						}						
						pField=&pNxCoreMsg->coreData.Category.pnxFields[9];	 
						if (pField->Set) {
							//item = gcnew String (pField->data.StringZ);
							//Console::WriteLine ("Quoted Unit: " + item);
						} 			 
						pField=&pNxCoreMsg->coreData.Category.pnxFields[10];	 
						if (pField->Set) {
							//item = gcnew String (pField->data.StringZ);
							//Console::WriteLine ("Tick Value: " + item);
						}
						InstrumentInfo^ pInstrumentInfo = gcnew InstrumentInfo (
							pInstrumentCache->MyInstrumentType,
							pDescription
						);
						pManaged->OnReferenceUpdate (pInstrumentKey, pInstrumentInfo, nullptr);
					}	break;
					case 80: { // FutureContract
						pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						if (pField->Set) {
							Console::WriteLine(String::Format("Expiration Date: {0} {1} {2}",
									pField->data.nxDate.Month,
									pField->data.nxDate.Day,
									pField->data.nxDate.Year
							));
						}
						pField=&pNxCoreMsg->coreData.Category.pnxFields[1];
						if (pField->Set) {
							Console::WriteLine(String::Format("First Delivery Date: {0} {1} {2}",
									pField->data.nxDate.Month,
									pField->data.nxDate.Day,
									pField->data.nxDate.Year
							));
						}
						InstrumentInfo^ pInstrumentInfo = gcnew InstrumentInfo (
							pInstrumentCache->MyInstrumentType,
							nullptr
						);
						pManaged->OnReferenceUpdate (pInstrumentKey, pInstrumentInfo, nullptr);
						//System::Diagnostics::Debugger::Break();
						} break;
					case 81: // OpenRange - we ignore this for now.
					case 82: // CloseRange - we ignore this for now.
					case 83: // OtherRange - we ignore this for now.
					case 84: // Settlement - we ignore this for now.
						break;
					case 86: { // FutureOptContract - this we need ;)
						//pField=&pNxCoreMsg->coreData.Category.pnxFields[0];
						//if (pField->Set) {
						//	Console::WriteLine(String::Format("Strike Price: {0}",
						//		pfNxPriceToDouble (
						//			pField->data.nxPrice.Price,
						//			pField->data.nxPrice.PriceType)
						//	));
						//}
						//pField=&pNxCoreMsg->coreData.Category.pnxFields[1];
						//if (pField->Set) {
						//	Console::WriteLine(String::Format("Expiration Date: {0} {1} {2}",
						//			pField->data.nxDate.Month,
						//			pField->data.nxDate.Day,
						//			pField->data.nxDate.Year
						//	));
						//}
						InstrumentInfo^ pInstrumentInfo = gcnew InstrumentInfo (
							pInstrumentCache->MyInstrumentType,
							nullptr
						);
						} break;
					default:
						String^ pCategoryString = gcnew String (pNxCoreMsg->coreData.Category.pnxStringCategory->String);
						Console::WriteLine (pCategoryString);
						break;
				}
				return NxCALLBACKRETURN_CONTINUE;
			}

			int NativeConnector::ProcessNxCoreSymbolChange (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				return NxCALLBACKRETURN_CONTINUE;
			}

			/*
				This is the SymbolSpin. In this, basicaly, we generate new InstrumentInfo items for every instrumnt
				that the spin has. We need to check performance here. It could be it may be smart to actually
				send the instrument info on a lower priority queue.
			*/
			int NativeConnector::ProcessNxCoreSymbolSpin (const NxCoreSystem* pNxCoreSys, const NxCoreMessage* pNxCoreMsg) {
				InstrumentKey^ pInstrumentKey = nullptr;
				InstrumentInfo^ pInstrumentInfo = nullptr;

				InstrumentCache^ pInstrumentCache = pCache->GetInstrumentCache (pNxCoreSys, pNxCoreMsg, pfNxGetSymbolAtom32());
				pInstrumentKey = pInstrumentCache->MyInstrumentKey;;

				// Lets dump all the data to the uplink.
				pManaged->OnReferenceUpdate (pInstrumentKey, pInstrumentInfo, nullptr);
				return NxCALLBACKRETURN_CONTINUE;
			}

		}
	}
}